package com.yohann.boot.config.aspect;

import com.yohann.boot.common.annotation.Lock;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author Yeung Wah
 * @since 2022/4/26
 */
@Slf4j
@Aspect
@Component
public class LockAspect {

    @Autowired
    private RedissonClient redissonClient;

    /**
     * SpEl解析器
     */
    private ExpressionParser parser = new SpelExpressionParser();

    @Pointcut("@annotation(com.yohann.boot.common.annotation.Lock)")
    public void lockAspect() {
    }

    @Around("lockAspect()")
    public Object log(ProceedingJoinPoint pjp) throws Throwable {
        //目标方法
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        String[] paramNames = signature.getParameterNames();
        Lock annotation = method.getAnnotation(Lock.class);

        //前缀生成
        String prefix = annotation.prefix();
        if (prefix.isEmpty()) {
            String className = pjp.getTarget().getClass().getName();
            String methodName = method.getName();
            prefix = String.join(":", className, methodName, Arrays.toString(paramNames));
        }

        //参数解析SpEl
        String key = annotation.key();
        Object[] args = pjp.getArgs();
        if (key.length() > 0 && args.length > 0) {
            //将参数名与参数值对应起来
            EvaluationContext context = new StandardEvaluationContext();
            for (int i = 0; i < paramNames.length; i++) {
                context.setVariable(paramNames[i], args[i]);
            }

            //根据SpEl表达式获取值
            Expression expression = parser.parseExpression(key);
            Object value = expression.getValue(context);
            key = Objects.toString(value, "");
        }

        //加锁
        long waitTime = annotation.waitTime(), leaseTime = annotation.leaseTime();
        TimeUnit timeUnit = annotation.timeUnit();
        String lockName = prefix + key;
        RLock lock = redissonClient.getLock(lockName);
        log.debug("get lock name: {}", lockName);
        boolean tryLock = lock.tryLock(waitTime, leaseTime, timeUnit);
        if (!tryLock) {
            throw new RuntimeException("get lock:" + lockName + " failed.");
        }
        try {
            return pjp.proceed();
        } finally {
            lock.unlock();
        }
    }

}